/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "ctrl.h"
#include "pspAll.h"
#include "Reminder.h"

typedef struct TEditAnniv {
	MAnnivDate *mda;
	BOOLEAN		ReminderEnabled;
} EDITANNIV, *LPEDITANNIV;

class DateCtrl
{
	HWND	_hwndDlg;
	HWND	_hwndDate;
	HWND	_hBtnMenu;
	HWND	_hBtnEdit;
	HWND	_hBtnAdd;
	HWND	_hBtnDel;

	BOOLEAN		_ReminderEnabled;

	MAnnivDate	**_pDates;
	WORD		 _numDates;
	WORD		 _curDate;

	BOOLEAN		ItemValid(WORD wIndex) const;
	BOOLEAN		CurrentItemValid() const;

public:
	DateCtrl(HWND hDlg);
	~DateCtrl();

	MAnnivDate*		Current()				{ return CurrentItemValid() ? _pDates[_curDate] : NULL; };
	WORD			CurrentIndex() const	{ return _curDate; };
	WORD			NumDates() const		{ return _numDates; };
	BOOLEAN			ReminderEnabled() const	{ return _ReminderEnabled; };

	MAnnivDate*		FindDateById(const WORD wId);

	VOID EnableCurrentItem();
	VOID EnableReminderCtrl(BOOLEAN bEnabled);

	INT	 SetCurSel(WORD wIndex);

	INT AddDate(MAnnivDate &mda);
	INT DeleteDate(WORD wIndex);

	INT DBGetBirthDay(HANDLE hContact, LPCSTR pszBaseProto);
	INT DBWriteBirthDay(HANDLE hContact);

	INT DBGetAnniversaries(HANDLE hContact);
	INT DBWriteAnniversaries(HANDLE hContact);

	// notification handlers
	VOID OnMenuPopup();
	VOID OnCurrentItemDateChanged(LPNMDATETIMECHANGE lpChange);
	VOID OnRemindEditChanged();
	VOID OnReminderChecked();
};

/**
 * name:	DateCtrl
 * class:	DateCtrl
 * desc:	constructor
 * param:	none
 * return:	nothing
 **/
DateCtrl::DateCtrl(HWND hDlg)
{
	_hwndDlg = hDlg;
	_hBtnAdd = GetDlgItem(hDlg, BTN_ADD);
	_hBtnDel = GetDlgItem(hDlg, BTN_DELETE);
	_hBtnEdit = GetDlgItem(hDlg, BTN_EDIT);
	_hBtnMenu = GetDlgItem(hDlg, BTN_MENU);
	_hwndDate = GetDlgItem(hDlg, EDIT_ANNIVERSARY_DATE);
	_ReminderEnabled = DBGetByte(SET_REMIND_ENABLED, DEFVAL_REMIND_ENABLED);

	_pDates = NULL;
	_curDate = 0;
	_numDates = 0;
	
	// set button tooltips
	SendMessage(_hBtnAdd, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Add a new anniversary"), MBF_TCHAR);
	SendMessage(_hBtnDel, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Delete an existing anniversary"), MBF_TCHAR);

	// limit textinput
	SendDlgItemMessage(_hwndDlg, EDIT_REMIND, EM_LIMITTEXT, 2, 0);
	SendDlgItemMessage(_hwndDlg, SPIN_REMIND, UDM_SETRANGE32, 0, 50);

	SetUserData(hDlg, this);
	
	// birthday is shown as an item in any case
	{
		MAnnivDate mdb;

		mdb.Id(ANID_BIRTHDAY);
		mdb.Description(TranslateT("Birthday"));
		AddDate(mdb);
	}
}

/**
 * name:	DateCtrl
 * class:	DateCtrl
 * desc:	destructor
 * param:	none
 * return:	nothing
 **/
DateCtrl::~DateCtrl()
{
	WORD i;

	if(_pDates != NULL) {
		for(i = 0; i < _numDates; i++) {
			delete _pDates[i];
		}
		free(_pDates);
	}
}

/**
 * name:	DateCtrl
 * class:	ItemValid
 * desc:	tests whether the item pointed to by the wIndex is valid or not
 * param:	wIndex	- index to desired item
 * return:	TRUE if item is valid, FALSE otherwise
 **/
BOOLEAN DateCtrl::ItemValid(WORD wIndex) const
{
	return (_pDates != NULL && wIndex < _numDates && _pDates[wIndex] != NULL);
}

/**
 * name:	DateCtrl
 * class:	CurrentItemValid
 * desc:	checks, whether currently selected item is valid
 * param:	none
 * return:	TRUE if item is valid, FALSE otherwise
 **/
BOOLEAN DateCtrl::CurrentItemValid() const
{
	return ItemValid(_curDate);
}

/**
 * name:	DateCtrl
 * class:	EnableReminderCtrl
 * desc:	enables or disables reminder controls
 * param:	none
 * return:	TRUE if item is valid, FALSE otherwise
 **/
VOID DateCtrl::EnableReminderCtrl(BOOLEAN bEnabled)
{
	bEnabled &= _ReminderEnabled != NReminder::REMIND_OFF;
	EnableWindow(GetDlgItem(_hwndDlg, CHECK_REMIND), bEnabled);
	EnableWindow(GetDlgItem(_hwndDlg, EDIT_REMIND), bEnabled);
	EnableWindow(GetDlgItem(_hwndDlg, SPIN_REMIND), bEnabled);
	EnableWindow(GetDlgItem(_hwndDlg, TXT_REMIND), bEnabled);
}

/**
 * name:	EnableCurrentItem
 * class:	DateCtrl
 * desc:	make control readonly if required
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::EnableCurrentItem()
{
	MAnnivDate *pCurrent = Current();

	if(pCurrent) {
		HANDLE hContact;
	
		PSGetContact(_hwndDlg, hContact);

		const BOOLEAN bEnabled
			= !hContact ||
			  (pCurrent->Flags() & CTRLF_HASCUSTOM) || 
			  !(pCurrent->Flags() & (CTRLF_HASPROTO|CTRLF_HASMETA)) ||
			  !DBGetByte(SET_PROPSHEET_PCBIREADONLY, 0);

		EnableWindow(_hBtnEdit, bEnabled);
		EnableWindow(_hBtnDel, bEnabled);
		EnableWindow(_hwndDate, bEnabled);
	}
}

/**
 * name:	FindDateById
 * class:	DateCtrl
 * desc:	returns an iterator to an item with the given id
 * param:	wId		- id the returned item must have
 * return:	if an date with the wId was found - iterator to item,
 *			NULL otherwise
 **/
MAnnivDate* DateCtrl::FindDateById(const WORD wId)
{
	WORD i;

	if(_pDates != NULL) {
		for(i = 0; i < _numDates; i++) {
			if(_pDates[i]->Id() < ANID_NONE && _pDates[i]->Id() == wId) {
				return _pDates[i];
			}
		}
	}
	return NULL;
}

/**
 * name:	AddDate
 * class:	DateCtrl
 * desc:	Add a new item to the array of dates
 * param:	mda		- the date to add
 * return:	0 on success, -1 on failure, 1 if the item to change was edited before and the new item was not set
 **/
INT DateCtrl::AddDate(MAnnivDate &mda)
{
	MAnnivDate *pmda, **pmd;

	// if a date with wID exists, replace it
	if((pmda = FindDateById(mda.Id())) != NULL) {
		BOOLEAN bChanged = pmda->IsChanged(),
			bRemindChanged = pmda->IsReminderChanged();

		if(!bChanged) {
			pmda->Set(mda);
			pmda->Module(mda.Module());
			pmda->Description(mda.Description());
			pmda->Flags(mda.Flags());
		}
		if(!bRemindChanged) {
			pmda->RemindOption(mda.RemindOption());
			pmda->RemindOffset(mda.RemindOffset());
		}
		return bChanged || bRemindChanged;
	}
	if(mda.Id() == ANID_NONE)
		mda.Id(_numDates - 1);

	if(pmd = (MAnnivDate **)realloc(_pDates, (_numDates + 1) * sizeof(pmda))) {
		_pDates = pmd;
		if(_pDates[_numDates] = new MAnnivDate(mda)) {
			_numDates++;
			return 0;
		}
	}
	return 1;
}

/**
 * name:	DeleteDate
 * class:	DateCtrl
 * desc:	Delete the item on the position identified by wIndex
 * param:	pDateCtrl	- pointer to the date control's data structure
 *			wIndex		- index of the item to delete
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DeleteDate(WORD wIndex)
{
	if(!ItemValid(wIndex)) return 1;
	
	// only delete values, but not the item
	if(_pDates[wIndex]->Id() == ANID_BIRTHDAY) {
		HANDLE	hContact;
		LPCSTR	pszBaseProto;

		PSGetContact(_hwndDlg, hContact);
		PSGetBaseProto(_hwndDlg, pszBaseProto);

		// protocol value exists?
		if(_pDates[wIndex]->DBGetDate(hContact, pszBaseProto, SET_CONTACT_BIRTHDAY, SET_CONTACT_BIRTHMONTH, SET_CONTACT_BIRTHYEAR)) {
			_pDates[wIndex]->SetFlags(MAnnivDate::MADF_HASPROTO);
		}
		else {
			_pDates[wIndex]->ZeroDate();
		}

		_pDates[wIndex]->RemindOption(BST_INDETERMINATE);
		_pDates[wIndex]->RemindOffset((BYTE)-1);

		_pDates[wIndex]->RemoveFlags(MAnnivDate::MADF_HASCUSTOM);
		_pDates[wIndex]->SetFlags(MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_REMINDER_CHANGED);
	}
	else {
		delete _pDates[wIndex];
		_numDates--;
		if(wIndex < _numDates)
			memmove(_pDates + wIndex, _pDates + wIndex + 1, (_numDates - wIndex) * sizeof(*_pDates));
		ZeroMemory(_pDates + _numDates, sizeof(*_pDates));
		if(_curDate >= _numDates)
			_curDate = _numDates - 1;
	}
	SendMessage(GetParent(_hwndDlg), PSM_CHANGED, NULL, NULL);
	SetCurSel(_curDate);
	return 0;
}

/**
 * name:	DateCtrl_DBGetBirthDay
 * desc:
 * param:
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBGetBirthDay(HANDLE hContact, LPCSTR pszBaseProto)
{
	MAnnivDate mdb;

	if(!mdb.DBGetBirthDate(hContact, pszBaseProto)) {
		mdb.DBGetReminderOpts(hContact);
		return AddDate(mdb) > 0;
	}
	return 0;
}

/**
 * name:	DateCtrl_DBGetBirthDay
 * desc:
 * param:
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBGetAnniversaries(HANDLE hContact)
{
	MAnnivDate mda;

	WORD i;
	BOOLEAN bChanged = FALSE;

	for(i = 0; i < ANID_LAST && !mda.DBGetAnniversaryDate(hContact, i); i++) {
		mda.DBGetReminderOpts(hContact);
		switch(AddDate(mda)) {
			case -1:
				return bChanged;
			case 1:
				bChanged |= 1;
				break;
		}
	}
	return bChanged;
}

/**
 * name:	DBWriteBirthDay
 * class:	DateCtrl
 * desc:	writes the birthday for a contact to database
 * param:	hContact - the contact to write the anniversaries to
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBWriteBirthDay(HANDLE hContact)
{
	MAnnivDate *pmdb;

	if((pmdb = FindDateById(ANID_BIRTHDAY)) == NULL)
		return 1;
		
	if(pmdb->IsChanged()) {
		// save birthday, to mBirthday module by default
		if(pmdb->Flags() & pmdb->MADF_HASCUSTOM)
			pmdb->DBWriteBirthDate(hContact);
		else
			pmdb->DBDeleteBirthDate(hContact);
	}

	if(pmdb->IsReminderChanged()) {
		pmdb->DBWriteReminderOpts(hContact);
	}
	pmdb->RemoveFlags(MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_REMINDER_CHANGED);
	return 0;
}

/**
 * name:	DBWriteAnniversaries
 * class:	DateCtrl
 * desc:	write all anniversaries to the database
 * param:	hContact - the contact to write the anniversaries to
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::DBWriteAnniversaries(HANDLE hContact)
{
	const LPCSTR szPrefix[] = { "Reminder", "Offset", "Desc", "Day", "Month", "Year", "Stamp", "Date" };
	CHAR szSet0[MAXSETTING];
	WORD i, ret, ofs, wIndex = 0;

	for(i = 0; i < _numDates; i++) {
		if(
			_pDates[i] != NULL &&
			!_pDates[i]->DBWriteAnniversaryDate(hContact, wIndex) &&
			!_pDates[i]->DBWriteReminderOpts(hContact)
		  )
		{
			wIndex++;
		}
	}
	// delete reluctant items
	do {
		ofs = mir_snprintfA(szSet0, SIZEOF(szSet0), "Anniv%d", wIndex);
		ret = 1;
		for(i = 0; i < SIZEOF(szPrefix); i++) {
			mir_strncpy(szSet0 + ofs, szPrefix[i], SIZEOF(szSet0) - ofs);
			ret &= DBDeleteContactSetting(hContact, USERINFO, szSet0);
		}
	}
	while(wIndex++ <= ANID_LAST && !ret);
	return 0;
}

/**
 * name:	SetCurSel
 * class:	DateCtrl
 * desc:	shows the item, identified by wIndex
 * param:	pDateCtrl	- pointer to the date control's data structure
 *			wIndex		- index of the item to delete
 * return:	0 on success 1 otherwise
 **/
INT DateCtrl::SetCurSel(WORD wIndex)
{
	BOOLEAN bEnabled = ItemValid(wIndex);

	EnableWindow(_hwndDate, bEnabled);
	EnableWindow(_hBtnEdit, bEnabled);
	EnableWindow(_hBtnDel, bEnabled && _pDates[wIndex]->IsValid());
	if(!bEnabled) {
		EnableReminderCtrl(FALSE);
		return 1;
	}
	_curDate = wIndex;

	// set date of date control
	if(_pDates[wIndex]->IsValid()) {
		SYSTEMTIME st = _pDates[wIndex]->SystemTime();
		DateTime_SetSystemtime(_hwndDate, GDT_VALID, &st);
		DateTime_SetFormat(_hwndDate, NULL);
	}
	else {
		TCHAR szText[MAX_DESC];
		mir_sntprintf(szText, MAX_DESC, _T("'%s'"), TranslateT("Unspecified"));
		DateTime_SetSystemtime(_hwndDate, GDT_NONE, NULL);
		DateTime_SetFormat(_hwndDate, szText);
	}
	// set edit button's caption
	SetWindowText(_hBtnEdit, _pDates[wIndex]->Description());

	// set reminder options
	CheckDlgButton(_hwndDlg, CHECK_REMIND, _pDates[wIndex]->RemindOption());
	OnReminderChecked();
	EnableCurrentItem();
	return 0;
}

/**
 * name:	OnMenuPopup
 * class:	DateCtrl
 * desc:	is called to show a popup menu for all anniversaries of a contact
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnMenuPopup()
{
	POINT pt = { 0, 0 };
	RECT rc;
	MENUITEMINFO mii;
	HMENU hMenu;
	WORD i;

	if(hMenu = CreatePopupMenu()) {
		SetFocus(_hBtnMenu);

		ZeroMemory(&mii, sizeof(MENUITEMINFO));
		mii.cbSize = sizeof(MENUITEMINFO);
		mii.fMask = MIIM_ID|MIIM_STRING|MIIM_STATE;

		// insert the items
		for(i = 0; i < _numDates; i++) {
			mii.fState = _pDates[i]->IsValid() ? MFS_CHECKED : MFS_UNCHECKED;
			mii.dwTypeData = (LPTSTR)_pDates[i]->Description();
			mii.wID = WM_USER + i;
			if(!InsertMenuItem(hMenu, i, TRUE, &mii)) {
				DestroyMenu(hMenu);
				return;
			}
		}
		ClientToScreen(_hBtnMenu, &pt);
		GetClientRect(_hBtnMenu, &rc);
		i = TrackPopupMenuEx(hMenu, TPM_RIGHTALIGN|TPM_RETURNCMD, pt.x + rc.right, pt.y + rc.bottom, _hwndDlg, NULL);
		DestroyMenu(hMenu);
		SendMessage(_hBtnMenu, BM_SETCHECK, NULL, NULL);
		if(i >= WM_USER) SetCurSel(i - WM_USER);
	}
}

/**
 * name:	OnMenuPopup
 * class:	DateCtrl
 * desc:	is called to show a popup menu for all anniversaries of a contact
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnCurrentItemDateChanged(LPNMDATETIMECHANGE lpChange)
{
	MAnnivDate *pCurrent = Current();

	if(pCurrent && !pCurrent->IsEqual(lpChange->st)) {
		HWND hPs = GetParent(_hwndDlg);

		// save the new date to the structure
		DateTime_SetFormat(_hwndDate, NULL);
		pCurrent->Set(lpChange->st, TRUE);
		pCurrent->SetFlags(MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_HASCUSTOM);
		
		// notify parent of the change
		SendMessage(hPs, PSM_CHANGED, NULL, NULL);
		EnableWindow(_hBtnDel, TRUE);

		// update the age and zodiac controls on the general propertysheetpage
		if(pCurrent->Id() == ANID_BIRTHDAY) {
			HWND hwndGeneral;
			// update the control
			if(SendMessage(hPs, PSM_GETPAGEHWND, IDD_CONTACT_GENERAL, (LPARAM)ghInst) && (hwndGeneral = (HWND)GetWindowLongPtr(hPs, DWL_MSGRESULT))) {
				SendMessage(hwndGeneral, PSPM_SETAGEANDZIDIAC, NULL, (LPARAM)pCurrent);
			}
		}
	}
}

/**
 * name:	OnRemindEditChanged
 * class:	DateCtrl
 * desc:	is called, if reminder edit control was changed
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnRemindEditChanged()
{
	MAnnivDate	*pCurrent = Current();

	if(pCurrent) {
		UINT iVal = GetDlgItemInt(_hwndDlg, EDIT_REMIND, NULL, FALSE);

		if(iVal != pCurrent->RemindOffset() && IsDlgButtonChecked(_hwndDlg, CHECK_REMIND) == BST_CHECKED) {
			SendMessage(GetParent(_hwndDlg), PSM_CHANGED, NULL, NULL);
			pCurrent->SetFlags(MAnnivDate::MADF_REMINDER_CHANGED);
			pCurrent->RemindOffset(iVal);
		}
	}
}

/**
 * name:	OnReminderChecked
 * class:	DateCtrl
 * desc:	is called if reminder checkbox's state was changed
 * param:	none
 * return:	nothing
 **/
VOID DateCtrl::OnReminderChecked()
{
	HANDLE hContact;
	LPCSTR szBaseProto;
	INT state;
	TCHAR buf[6];
	MAnnivDate *pCurrent = Current();

	PSGetContact(_hwndDlg, hContact);
	if(!hContact || !PSGetBaseProto(_hwndDlg, szBaseProto) || !pCurrent) {
		EnableReminderCtrl(FALSE);
		return;
	}
	if(pCurrent->RemindOption() != (state = IsDlgButtonChecked(_hwndDlg, CHECK_REMIND))) {
		pCurrent->RemindOption(state);
		if(!PspIsLocked(_hwndDlg)) {
			pCurrent->SetFlags(MAnnivDate::MADF_REMINDER_CHANGED);
			SendMessage(GetParent(_hwndDlg), PSM_CHANGED, NULL, NULL);
		}
	}
	switch(state) {
		case BST_CHECKED:
			if(pCurrent->RemindOffset() == (BYTE)-1)
				_itot(DBGetByte(SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET), buf, 10);
			else
				_itot(pCurrent->RemindOffset(), buf, 10);
			EnableWindow(GetDlgItem(_hwndDlg, EDIT_REMIND), _ReminderEnabled);
			EnableWindow(GetDlgItem(_hwndDlg, SPIN_REMIND), _ReminderEnabled);
			break;
		case BST_UNCHECKED:
			*buf = 0;
			EnableWindow(GetDlgItem(_hwndDlg, EDIT_REMIND), FALSE);
			EnableWindow(GetDlgItem(_hwndDlg, SPIN_REMIND), FALSE);
			break;
		case BST_INDETERMINATE:
			_itot(DBGetByte(SET_REMIND_OFFSET, DEFVAL_REMIND_OFFSET), buf, 10);
			EnableWindow(GetDlgItem(_hwndDlg, EDIT_REMIND), FALSE);
			EnableWindow(GetDlgItem(_hwndDlg, SPIN_REMIND), FALSE);
			break;
	}
	SetDlgItemText(_hwndDlg, EDIT_REMIND, buf);
}

/**
 * name:	DlgProc_AnniversaryEditor
 * desc:
 * param:
 * return:	0 on success 1 otherwise
 **/
INT CALLBACK DlgProc_AnniversaryEditor(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg) {
		case WM_INITDIALOG:
		{
			SYSTEMTIME st;
			LPEDITANNIV editAnniv = (LPEDITANNIV)lParam;

			if(!PtrIsValid(editAnniv) || !editAnniv->mda) break;
			SetUserData(hDlg, lParam);

			if(!editAnniv->mda->Description() || editAnniv->mda->IsValid())
				SetWindowText(hDlg, _T("Edit Anniversary"));

			// set icons
			{
				const NIcoLib::ICONCTRL idIcon[] = {
					{ ICO_DLG_ANNIVERSARY,	WM_SETICON,		NULL		},
					{ ICO_DLG_ANNIVERSARY,	STM_SETIMAGE,	ICO_DLGLOGO	},
					{ ICO_BTN_OK,			BM_SETIMAGE,	IDOK		},
					{ ICO_BTN_CANCEL,		BM_SETIMAGE,	IDCANCEL	}
				};
				const INT numIconsToSet = DBGetByte(SET_ICONS_BUTTONS, 1) ? SIZEOF(idIcon) : 2;
				
				NIcoLib::SetCtrlIcons(hDlg, idIcon, numIconsToSet);
			}

			// translate controls
			SendDlgItemMessage(hDlg, IDOK, BUTTONTRANSLATE, NULL, NULL);
			SendDlgItemMessage(hDlg, IDCANCEL, BUTTONTRANSLATE, NULL, NULL);
			TranslateDialogDefault(hDlg);

			// init controls
			EnableWindow(GetDlgItem(hDlg, EDIT_CATEGORY), editAnniv->mda->Id() != ANID_BIRTHDAY);
			SetDlgItemText(hDlg, EDIT_CATEGORY, editAnniv->mda->Description());
			st = editAnniv->mda->SystemTime();
			MonthCal_SetCurSel(GetDlgItem(hDlg, EDIT_ANNIVERSARY_DATE), &st);
			EnableWindow(GetDlgItem(hDlg, CHECK_REMIND), editAnniv->ReminderEnabled);
			CheckDlgButton(hDlg, CHECK_REMIND, editAnniv->mda->RemindOffset());
			return TRUE;
		}

		case WM_CTLCOLORSTATIC:
			switch(GetDlgCtrlID((HWND)lParam)) {
				case STATIC_WHITERECT:
				case ICO_DLGLOGO:
				case TXT_NAME:
					SetBkColor((HDC)wParam, RGB(255, 255, 255));
					return (BOOL)GetStockObject(WHITE_BRUSH);
			}
			break;

		case WM_COMMAND:

			switch(LOWORD(wParam)) {
				case EDIT_CATEGORY:
					if(HIWORD(wParam) == EN_UPDATE) {
						EnableWindow(GetDlgItem(hDlg, IDOK), GetWindowTextLength((HWND)lParam) > 0);
					}
					break;

				case IDOK:
				{
					LPEDITANNIV editAnniv = (LPEDITANNIV)GetUserData(hDlg);

					// read new description
					{
						HWND hEdit = GetDlgItem(hDlg, EDIT_CATEGORY);
						INT len = Edit_GetTextLength(hEdit);
						LPTSTR pszText;

						if(
							len == 0 ||
							(pszText = (LPTSTR)_alloca((len + 1) * sizeof(TCHAR))) == NULL ||
							!Edit_GetText(hEdit, pszText, len + 1)
						  )
						{
							MsgErr(hDlg, _T("Please enter a valid Description first!"));
							break;
						}

						if(_tcsicmp(pszText, editAnniv->mda->Description())) {
							editAnniv->mda->Description(pszText);
							editAnniv->mda->SetFlags(MAnnivDate::MADF_HASCUSTOM|MAnnivDate::MADF_CHANGED);
						}
					}
					
					// get new date
					{
						SYSTEMTIME st;
						MonthCal_GetCurSel(GetDlgItem(hDlg, EDIT_ANNIVERSARY_DATE), &st);
						if(!editAnniv->mda->IsEqual(st)) {
							editAnniv->mda->SetDate(st);
							editAnniv->mda->SetFlags(MAnnivDate::MADF_HASCUSTOM|MAnnivDate::MADF_CHANGED);
						}
					}

					// get new reminder option
					{
						BYTE state;
						if((state = IsDlgButtonChecked(hDlg, CHECK_REMIND)) != editAnniv->mda->RemindOption()) {
							editAnniv->mda->SetFlags(MAnnivDate::MADF_REMINDER_CHANGED);
							editAnniv->mda->RemindOption(state);
						}
					}
				}
				case IDCANCEL:
					return EndDialog(hDlg, LOWORD(wParam));
			}
			break;
	}
	return FALSE;
}

/**
 * name:	DlgProc_pspOrigin
 * desc:	dialog procedure for the advanced contact information propertysheetpage
 * param:	hDlg	- handle to the dialog window
 *			uMsg	- the message to handle
 *			wParam	- parameter
 *			lParam	- parameter
 * return:	different values
 **/
INT CALLBACK DlgProc_pspOrigin(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	DateCtrl* pDateCtrl = (DateCtrl*)GetUserData(hDlg);

	switch(uMsg) {
		
		case WM_INITDIALOG:
		{
			// create and initiate the data structure
			if(!(pDateCtrl = new DateCtrl(hDlg)))
				return FALSE;
			// hContact == NULL or reminder disabled
			pDateCtrl->EnableReminderCtrl(lParam != NULL);

			TranslateDialogDefault(hDlg);
			Ctrl_InitTextColours();	
			return TRUE;
		}

		case WM_CTLCOLOREDIT:
			if((HWND)lParam && DBGetByte(SET_PROPSHEET_SHOWCOLOURS, TRUE)) {
				LPCTRL pCtrl = (LPCTRL)GetUserData((HWND)lParam);
				if(PtrIsValid(pCtrl)) return Ctrl_SetTextColour((HDC)wParam, pCtrl->wFlags);
			}
			return FALSE;

		case WM_CTLCOLORSTATIC:
			switch(GetWindowLong((HWND)lParam, GWL_ID)) {
				case EDIT_STREET:
				case EDIT_CITY:
				case EDIT_ZIP:
				case EDIT_STATE:
				case EDIT_COUNTRY:
				case EDIT_ANNIVERSARY_DATE:
					return (BOOL)GetStockObject(WHITE_BRUSH);
			}
			return FALSE;

		case WM_NOTIFY:
			switch(((LPNMHDR)lParam)->idFrom) {
				case 0:
				{
					HANDLE hContact = (HANDLE)((LPPSHNOTIFY)lParam)->lParam;
					LPCSTR szBaseProto;
					
					switch(((LPNMHDR)lParam)->code) {
						case PSN_INFOCHANGED:
						{
							BYTE bChanged = 0;
							LPIDSTRLIST pList;
							UINT nList;

							if(!PSGetBaseProto(hDlg, szBaseProto) || *szBaseProto == 0) break;

							//
							// origin groupbox
							//
							bChanged |= Edit_SetValue(hDlg, EDIT_STREET, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_STREET);
							bChanged |= Edit_SetValue(hDlg, EDIT_ZIP, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_ZIP);
							bChanged |= Edit_SetValue(hDlg, EDIT_CITY, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_CITY);
							bChanged |= Edit_SetValue(hDlg, EDIT_STATE, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_STATE);
							GetCountryList((WPARAM)&nList, (LPARAM)&pList);
							bChanged |= ComboBox_SelectItem(hDlg, EDIT_COUNTRY, pList, nList, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_COUNTRY, 0);
							ComboBox_CountrySelChanged(hDlg, GetDlgItem(hDlg, EDIT_COUNTRY), GetDlgItem(hDlg, ICO_COUNTRY));

							//
							// birthday groupbox
							//
							if(PtrIsValid(pDateCtrl)) {
								bChanged |= pDateCtrl->DBGetBirthDay(hContact, szBaseProto);
								bChanged |= pDateCtrl->DBGetAnniversaries(hContact);
								pDateCtrl->SetCurSel(0);
							}
							// load date and time, an contact was added to clist (works only for icq at the moment)
							{
								MTime mt;

								if(mt.DBGetStamp(hContact, USERINFO, SET_CONTACT_ADDEDTIME)) {
									DWORD dwStamp = WhenWasContactAdded(DBGetContactSettingDword(hContact, szBaseProto, "UIN", 0), szBaseProto);
									if(dwStamp > 0) mt.FromStampAsLocal(dwStamp);
								}
								if(mt.IsValid()) {
									TCHAR szTime[MAX_PATH];
									LPTSTR ptr;
									mt.DateFormatLong(szTime, SIZEOF(szTime));
									_tcscat(szTime, _T(" - "));
									ptr = szTime + _tcslen(szTime);
									mt.TimeFormat(ptr, SIZEOF(szTime) - (ptr - szTime));
									SetDlgItemText(hDlg, TXT_DATEADDED, szTime);
								}
							}

							SetWindowLongPtr(hDlg, DWL_MSGRESULT, bChanged ? PSP_CHANGED : 0);
							break;
						}
						
						case PSN_APPLY:
						{
							BYTE state = (BYTE)SendDlgItemMessage(hDlg, CHECK_REMIND, BM_GETCHECK, NULL, NULL);

							if(!PSGetBaseProto(hDlg, szBaseProto) || *szBaseProto == 0) break;

							// save origin
							Edit_WriteValue(hDlg, EDIT_STREET, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_STREET);
							Edit_WriteValue(hDlg, EDIT_ZIP, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_ZIP);
							Edit_WriteValue(hDlg, EDIT_CITY, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_CITY);
							Edit_WriteValue(hDlg, EDIT_STATE, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_STATE);
							ComboBox_WriteItem(hDlg, EDIT_COUNTRY, hContact, USERINFO, szBaseProto, SET_CONTACT_ORIGIN_COUNTRY, DBVT_WORD);

							// save anniversaries
							if(PtrIsValid(pDateCtrl)) {
								pDateCtrl->DBWriteBirthDay(hContact);
								pDateCtrl->DBWriteAnniversaries(hContact);
								NReminder::OnCListApplyIcons((WPARAM)hContact, 0);
							}
							break;
						}
						case PSN_ICONCHANGED:
						{
							const NIcoLib::ICONCTRL idIcon[] = {
								{ ICO_COMMON_ADDRESS,	STM_SETIMAGE,	ICO_ADDRESS  },
								{ ICO_COMMON_BIRTHDAY,	STM_SETIMAGE,	ICO_BIRTHDAY },
								{ ICO_BTN_ADD,			BM_SETIMAGE,	BTN_ADD		 },
								{ ICO_BTN_DELETE,		BM_SETIMAGE,	BTN_DELETE	 }
							};

							NIcoLib::SetCtrlIcons(hDlg, idIcon, SIZEOF(idIcon));
							break;
						}
					}
					break; /* case 0 */
				}

				case EDIT_ANNIVERSARY_DATE:
					if(!PspIsLocked(hDlg) && PtrIsValid(pDateCtrl)) {
						LPNMHDR lpNmhdr = (LPNMHDR)lParam;
						
						switch(lpNmhdr->code) {
							case DTN_DATETIMECHANGE:
								pDateCtrl->OnCurrentItemDateChanged((LPNMDATETIMECHANGE) lParam);
								break;
							case DTN_DROPDOWN:
							{
								HWND hMonthCal = DateTime_GetMonthCal(lpNmhdr->hwndFrom);
								SetWindowLong(hMonthCal, GWL_STYLE, GetWindowLong(hMonthCal, GWL_STYLE)|MCS_WEEKNUMBERS);
								break;
							}
						}
					}
					break;
			} /* switch(((LPNMHDR)lParam)->idFrom) */
			break;

		case WM_COMMAND:
			switch(LOWORD(wParam)) {
				case EDIT_STREET:
					if(!PspIsLocked(hDlg) && HIWORD(wParam) == EN_UPDATE)
						Edit_Changed(hDlg, LOWORD(wParam), USERINFO, SET_CONTACT_ORIGIN_STREET);
					break;
				case EDIT_ZIP:
					if(!PspIsLocked(hDlg) && HIWORD(wParam) == EN_UPDATE)
						Edit_Changed(hDlg, LOWORD(wParam), USERINFO, SET_CONTACT_ORIGIN_ZIP);
					break;
				case EDIT_CITY:
					if(!PspIsLocked(hDlg) && HIWORD(wParam) == EN_UPDATE)
						Edit_Changed(hDlg, LOWORD(wParam), USERINFO, SET_CONTACT_ORIGIN_CITY);
					break;
				case EDIT_STATE:
					if(!PspIsLocked(hDlg) && HIWORD(wParam) == EN_UPDATE)
						Edit_Changed(hDlg, LOWORD(wParam), USERINFO, SET_CONTACT_ORIGIN_STATE);
					break;
				case EDIT_COUNTRY:
					switch(HIWORD(wParam)) {
						case CBN_SETFOCUS:
						case CBN_DROPDOWN:
							ComboBox_DropDown((HWND)lParam);
							break;
						case CBN_SELCHANGE:
							ComboBox_CountrySelChanged(hDlg, (HWND)lParam, GetDlgItem(hDlg, ICO_COUNTRY));
							break;
					}
					break;
				case EDIT_REMIND:
					if(!PspIsLocked(hDlg) && HIWORD(wParam) == EN_UPDATE)
						pDateCtrl->OnRemindEditChanged();
					break;

				/**
				 * name:	BTN_MENU
				 * desc:	the button to dropdown the list to show all items is pressed
				 **/
				case BTN_MENU:
					if(HIWORD(wParam) == BN_CLICKED)
						pDateCtrl->OnMenuPopup();
					break;

				/**
				 * name:	BTN_ADD
				 * desc:	creates a new dialog to add a new anniversary
				 **/
				case BTN_ADD:
					if(HIWORD(wParam) == BN_CLICKED && PtrIsValid(pDateCtrl)) {
						MAnnivDate Date;
						EDITANNIV  editAnniv;
						
						editAnniv.mda = &Date;
						editAnniv.ReminderEnabled = pDateCtrl->ReminderEnabled();

						if(IDOK == DialogBoxParam(ghInst, MAKEINTRESOURCE(IDD_ANNIVERSARY_EDITOR), hDlg, DlgProc_AnniversaryEditor, (LPARAM)&editAnniv)) { 
							SendMessage(GetParent(hDlg), PSM_CHANGED, NULL, NULL);
							if(!pDateCtrl->AddDate(Date))
								pDateCtrl->SetCurSel(pDateCtrl->NumDates() - 1);
						}
					}
					break;

				/**
				 * name:	BTN_EDIT
				 * desc:	edit the currently selected anniversary
				 **/
				case BTN_EDIT:
					if(HIWORD(wParam) == BN_CLICKED && PtrIsValid(pDateCtrl)) {
						EDITANNIV editAnniv;

						if((editAnniv.mda = pDateCtrl->Current()) == NULL) {
							MsgErr(hDlg, _T("No valid date selected for editing!"));
							break;	
						}
						editAnniv.ReminderEnabled = pDateCtrl->ReminderEnabled();
						if(
							IDOK == DialogBoxParam(ghInst, MAKEINTRESOURCE(IDD_ANNIVERSARY_EDITOR), hDlg, DlgProc_AnniversaryEditor, (LPARAM)&editAnniv)&&
							(editAnniv.mda->Flags() & (MAnnivDate::MADF_CHANGED|MAnnivDate::MADF_REMINDER_CHANGED))
						  )
						{ 
							SendMessage(GetParent(hDlg), PSM_CHANGED, NULL, NULL);
							pDateCtrl->SetCurSel(pDateCtrl->CurrentIndex());
						}
					}
					break;

				/**
				 * name:	BTN_DELETE
				 * desc:	user wants to delete an anniversary
				 **/
				case BTN_DELETE:
				{
					MAnnivDate *pCurrent;

					if(
						HIWORD(wParam) == BN_CLICKED &&
					    PtrIsValid(pDateCtrl) &&
					    (pCurrent = pDateCtrl->Current()) != NULL
					  )
					{
						MSGBOX mBox;
						TCHAR szMsg[MAXDATASIZE];
						
						mir_sntprintf(szMsg, MAXDATASIZE,
							TranslateT("Do you really want to delete the %s?"), 
							pCurrent->Description());

						mBox.uSize = sizeof(MSGBOX);
						mBox.hParent = hDlg;
						mBox.hiLogo = NIcoLib::GetIcon(ICO_DLG_ANNIVERSARY);
						mBox.hiMsg = NULL;
						mBox.uType = MB_YESNO|MB_ICON_QUESTION|MB_TCHAR;
						mBox.ptszTitle = _T("Delete");
						mBox.ptszInfoText = _T("Removes an item from the list");
						mBox.ptszMsg = szMsg;
						if(IDYES == MsgBoxService(NULL, (LPARAM)&mBox))
							pDateCtrl->DeleteDate(pDateCtrl->CurrentIndex());
					}
					break;
				}

				/**
				 * name:	CHECK_REMIND
				 * desc:	state of reminder checkbox is changed
				 **/
				case CHECK_REMIND:
					if(PtrIsValid(pDateCtrl) && HIWORD(wParam) == BN_CLICKED)
						pDateCtrl->OnReminderChecked();
					break;
			} /* switch(LOWORD(wParam)) */
			break;

		case PSPM_GETBIRTHDATE:
		{
			MAnnivDate *pbdFind, *pbd = (MAnnivDate*)lParam;

			if(!PtrIsValid(pbd) || !PtrIsValid(pDateCtrl)) break;

			if((pbdFind = pDateCtrl->FindDateById(ANID_BIRTHDAY)) != NULL) {
				pbd->SetDate(*pbdFind);
				return TRUE;
			}
			break;
		}
		case WM_DESTROY:
			Edit_Destroy(hDlg, EDIT_STREET);
			Edit_Destroy(hDlg, EDIT_ZIP);
			Edit_Destroy(hDlg, EDIT_CITY);
			Edit_Destroy(hDlg, EDIT_REMIND);
			Edit_Destroy(hDlg, EDIT_STATE);
			ComboBox_Destroy(hDlg, EDIT_COUNTRY);
			delete pDateCtrl;
			break;
	}
	return FALSE;
}
